package top.simonby.iron.plugin.data.jpa.query;

import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


/**
 * Specification 生成接口
 *
 * @author Simon
 * @param <M>
 */
public interface SpecGenerator<M extends SpecBaseQuery> {

    /**
     * 构建Specification
     *
     * @param logicType
     * @param outerThis
     * @param <T>
     * @return
     */
    <T> Specification<T> builderSpecification(String logicType, M outerThis);

    /**
     * 获取字段名称
     *
     * @param field
     * @return
     */
    default String getFieldName(Field field) {
        return field.getName();
    }

    /**
     * 获取类clazz的所有Field，包括其父类的Field
     *
     * @param clazz
     * @return
     */
    default List<Field> getAllFieldsWithRoot(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();

        //获取本类所有字段
        Field[] dFields = clazz.getDeclaredFields();
        if (null != dFields && dFields.length > 0) {
            fieldList.addAll(Arrays.asList(dFields));
        }

        // 若父类是Object，则直接返回当前Field列表
        Class<?> superClass = clazz.getSuperclass();
        if (superClass == Object.class) {
            return Arrays.asList(dFields);
        }

        // 递归查询父类的field列表
        List<Field> superFields = getAllFieldsWithRoot(superClass);

        if (null != superFields && !superFields.isEmpty()) {
            superFields.stream().
                    //不重复字段
                            filter(field -> !fieldList.contains(field)).
                    forEach(field -> fieldList.add(field));
        }
        return fieldList;
    }

    class DefaultSpecGenerator<M extends SpecBaseQuery> implements SpecGenerator<M> {

        @Override
        public <T> Specification<T> builderSpecification(String logicType, M outerThis) {
            return (root, criteriaQuery, cb) -> {
                Class clazz = outerThis.getClass();
                //获取查询类Query的所有字段,包括父类字段
                List<Field> fields = getAllFieldsWithRoot(clazz);
                List<Predicate> predicates = new ArrayList<>(fields.size());
                for (Field field : fields) {
                    //获取字段上的@QueryWord注解
                    SpecQuery qw = field.getAnnotation(SpecQuery.class);
                    if (qw == null) {
                        continue;
                    }
                    // 获取字段名
                    String column = qw.column();
                    //如果主注解上colume为默认值"",则以field为准
                    if (column.equals("")) {
                        column = getFieldName(field);
                    }
                    field.setAccessible(true);

                    try {
                        // nullable
                        Object value = field.get(outerThis);
                        //如果值为null,注解未标注nullable,跳过
                        if (value == null && !qw.nullable()) {
                            continue;
                        }

                        // can be empty
                        if (value != null && String.class.isAssignableFrom(value.getClass())) {
                            String s = (String) value;
                            //如果值为"",且注解未标注emptyable,跳过
                            if (s.equals("") && !qw.employable()) {
                                continue;
                            }
                        }

                        //通过注解上func属性,构建路径表达式
                        Path path = root.get(column);
                        switch (qw.func()) {
                            case equal:
                                predicates.add(cb.equal(path, value));
                                break;
                            case like:
                                predicates.add(cb.like(path, "%" + value + "%"));
                                break;
                            case gt:
                                predicates.add(cb.gt(path, (Number) value));
                                break;
                            case lt:
                                predicates.add(cb.lt(path, (Number) value));
                                break;
                            case ge:
                                predicates.add(cb.ge(path, (Number) value));
                                break;
                            case le:
                                predicates.add(cb.le(path, (Number) value));
                                break;
                            case notEqual:
                                predicates.add(cb.notEqual(path, value));
                                break;
                            case notLike:
                                predicates.add(cb.notLike(path, "%" + value + "%"));
                                break;
                            case greaterThan:
                                predicates.add(cb.greaterThan(path, (Comparable) value));
                                break;
                            case greaterThanOrEqualTo:
                                predicates.add(cb.greaterThanOrEqualTo(path, (Comparable) value));
                                break;
                            case lessThan:
                                predicates.add(cb.lessThan(path, (Comparable) value));
                                break;
                            case lessThanOrEqualTo:
                                predicates.add(cb.lessThanOrEqualTo(path, (Comparable) value));
                                break;
                        }
                    } catch (Exception e) {
                        continue;
                    }
                }
                Predicate p = null;
                if (logicType == null || logicType.equals("") || logicType.equals("and")) {
                    p = cb.and(predicates.toArray(new Predicate[predicates.size()]));//and连接
                } else if (logicType.equals("or")) {
                    p = cb.or(predicates.toArray(new Predicate[predicates.size()]));//or连接
                }
                return p;
            };
        }
    }
}
